Backport of pcre2-10.48-Fix-DFA-workspace-overflows.patch
authorNicholas Wilson <nicholas@nicholaswilson.me.uk>
Thu, 27 Aug 2026 15:52:16 +0000 (16:52 +0100)
committerMatthew Vernon <matthew@debian.org>
Tue, 1 Sep 2026 10:43:48 +0000 (11:43 +0100)
Cherry-pick of c932e70451eafef922ebef364ac25042f0031135

Fix DFA workspace overflows; see GHSA-3r4p-g7gg-ppmf for details

(cherry picked from commit bcaf1ba40748a27e1e666491a0e9887f0f5f2965)

src/pcre2_dfa_match.c
testdata/testinput6
testdata/testoutput6

index ebf31d284d27ee070adba857899d65f683618cf8..9911acf234d4ca4bef812e19c99babb37be50848 100644 (file)
@@ -407,8 +407,8 @@ return (mb->callout)(cb, mb->callout_data);
 
 /* This function is called when internal_dfa_match() is about to be called
 recursively and there is insufficient working space left in the current
-workspace block. If there's an existing next block, use it; otherwise get a new
-block unless the heap limit is reached.
+workspace block. If there's a sufficiently large next block, use it; get a new
+block unless the heap limit is (or has been) reached.
 
 Arguments:
   rwsptr     pointer to block pointer (updated)
@@ -424,9 +424,18 @@ more_workspace(RWS_anchor **rwsptr, unsigned int ovecsize, dfa_match_block *mb)
 {
 RWS_anchor *rws = *rwsptr;
 RWS_anchor *new;
+uint32_t requested;
+
+PCRE2_ASSERT(ovecsize <= UINT32_MAX - RWS_RSIZE - RWS_ANCHOR_SIZE);
+requested = RWS_RSIZE + ovecsize + RWS_ANCHOR_SIZE;
 
 if (rws->next != NULL)
   {
+  /* Although the initial block is large, and subsequent ones try to double, the
+  heap limit may cause the last one to be smaller; in this case, we have already
+  hit the heap limit and allocating a larger block will not be possible. */
+  if (rws->next->size < requested)
+    return PCRE2_ERROR_HEAPLIMIT;
   new = rws->next;
   }
 
@@ -436,14 +445,30 @@ overflow. */
 
 else
   {
-  uint32_t newsize = (rws->size >= UINT32_MAX/(sizeof(int)*2))? UINT32_MAX/sizeof(int) : rws->size * 2;
+  uint32_t newsize = (rws->size >= (UINT32_MAX/sizeof(int))/2)?
+    UINT32_MAX/sizeof(int) : rws->size * 2;
   uint32_t newsizeK = newsize/(1024/sizeof(int));
 
-  if (newsizeK + mb->heap_used > mb->heap_limit)
-    newsizeK = (uint32_t)(mb->heap_limit - mb->heap_used);
-  newsize = newsizeK*(1024/sizeof(int));
+  /* Clamp the allocation to the remaining heap allowance with care for overflows */
+
+  if (mb->heap_used >= mb->heap_limit)
+    {
+    newsize = 0;
+    newsizeK = 0;
+    }
+  else
+    {
+    PCRE2_SIZE availableK = mb->heap_limit - mb->heap_used;
+    /* newsize always capped at UINT32_MAX/sizeof(int), so newsizeK also capped;
+    and - if availableK is smaller - then multiplication to form newsize is safe */
+    if (newsizeK > availableK)
+      {
+      newsize = (uint32_t)(availableK*(1024/sizeof(int)));
+      newsizeK = availableK;
+      }
+    }
 
-  if (newsize < RWS_RSIZE + ovecsize + RWS_ANCHOR_SIZE)
+  if (newsize < requested)
     return PCRE2_ERROR_HEAPLIMIT;
   new = mb->memctl.malloc(newsize*sizeof(int), mb->memctl.memory_data);
   if (new == NULL) return PCRE2_ERROR_NOMEMORY;
@@ -2803,6 +2828,7 @@ for (;;)
 
         local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
         local_workspace = ((int *)local_offsets) + RWS_OVEC_OSIZE;
+        PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_OSIZE);
         rws->free -= RWS_RSIZE + RWS_OVEC_OSIZE;
 
         while (*endasscode == OP_ALT) endasscode += GET(endasscode, 1);
@@ -2902,6 +2928,7 @@ for (;;)
 
           local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
           local_workspace = ((int *)local_offsets) + RWS_OVEC_OSIZE;
+          PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_OSIZE);
           rws->free -= RWS_RSIZE + RWS_OVEC_OSIZE;
 
           while (*endasscode == OP_ALT) endasscode += GET(endasscode, 1);
@@ -2950,6 +2977,7 @@ for (;;)
 
         local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
         local_workspace = ((int *)local_offsets) + RWS_OVEC_RSIZE;
+        PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_RSIZE);
         rws->free -= RWS_RSIZE + RWS_OVEC_RSIZE;
 
         /* Check for repeating a recursion without advancing the subject
@@ -3049,6 +3077,7 @@ for (;;)
 
         local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
         local_workspace = ((int *)local_offsets) + RWS_OVEC_OSIZE;
+        PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_OSIZE);
         rws->free -= RWS_RSIZE + RWS_OVEC_OSIZE;
 
         if (codevalue == OP_BRAPOSZERO)
@@ -3148,6 +3177,7 @@ for (;;)
 
         local_offsets = (PCRE2_SIZE *)(RWS + rws->size - rws->free);
         local_workspace = ((int *)local_offsets) + RWS_OVEC_OSIZE;
+        PCRE2_ASSERT(rws->free >= RWS_RSIZE + RWS_OVEC_OSIZE);
         rws->free -= RWS_RSIZE + RWS_OVEC_OSIZE;
 
         rc = internal_dfa_match(
index 1fbe4ce8d47ba0e176bee249f096580b58b54e07..8b75c796d61a1e1728a9a5a320c22aa4a1cbd4d3 100644 (file)
 
 # --------------
 
+# Test workspace resizing and workspace re-use
+
+/(*LIMIT_HEAP=4)(?=(?=(?=(?=(?=(?=(?=(?=a))(?R)))))))./
+    a\=dfa
+
 # End of testinput6
index 74c40e904c8d9e9987aa46580fe18fe3f74c724f..802bd847cb49ae790773954edba4b70a2907179d 100644 (file)
@@ -8164,4 +8164,10 @@ No match
 
 # --------------
 
+# Test workspace resizing and workspace re-use
+
+/(*LIMIT_HEAP=4)(?=(?=(?=(?=(?=(?=(?=(?=a))(?R)))))))./
+    a\=dfa
+Failed: error -63: heap limit exceeded
+
 # End of testinput6